package com.starsoft.quant.executor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.starsoft.frame.base.service.UserService;
import com.starsoft.frame.util.DateUtil;
import com.starsoft.frame.util.StringUtil;
import com.starsoft.quant.analysis.IndicatorService;
import com.starsoft.quant.analysis.VolatilityService;
import com.starsoft.quant.bean.ModelContext;
import com.starsoft.quant.executor.ExecutorContext.ExecutMode;
import com.starsoft.quant.service.ModelParamsService;
import com.starsoft.quant.service.QuantAcountService;
import com.starsoft.quant.service.QuantReportService;
import com.starsoft.quant.strategy.Strategy;
import com.starsoft.smdc.bean.SmdcEr;
import com.starsoft.smdc.bean.SmdcMarketDaily;
import com.starsoft.smdc.bean.SmdcSecurity;
import com.starsoft.smdc.crawler.common.HandicapCrawlerImpl;
import com.starsoft.smdc.crawler.eastmoney.FundNavPreEastmoney;
import com.starsoft.smdc.service.DivService;
import com.starsoft.smdc.service.ErService;
import com.starsoft.smdc.service.HolidayService;
import com.starsoft.smdc.service.MarketDailyService;
import com.starsoft.smdc.service.SecurityService;
import com.starsoft.smdc.util.SecurityType;
import com.starsoft.trade.bean.TradeAcountStatus;
import com.starsoft.trade.service.AcountStatusService;
import com.starsoft.trade.service.TradeBillService;

import cn.hutool.core.collection.CollUtil;

@Component
public class StrategyExecutor {

	private final static Logger logger = LoggerFactory.getLogger(StrategyExecutor.class);// 日志
	
	@Autowired
	SecurityService securityService;

	@Autowired
	HandicapCrawlerImpl handicapService;

	@Autowired
	MarketDailyService marketDailyService;

	@Autowired
	QuantAcountService quantAcountService;

	@Autowired
	HolidayService holidayService;

	@Autowired
	ErService erService;

	@Autowired
	DivService divService;

	@Autowired
	ModelParamsService modelParamsService;
	
	@Autowired
	UserService userService;
	
	@Autowired
	QuantReportService reportService;
	
	@Autowired
	AcountStatusService acountStatusService;
	
	@Autowired
	ExecutorService executorService;
	
	@Autowired
	IndicatorService indicatorService;
	@Autowired
	TradeBillService tradeBillService;
	@Autowired
	FundNavPreEastmoney fundNavPreService;
	@Autowired 
	VolatilityService volatilityService;
	
	public StrategyReport executor(Integer acountId) throws Exception {
		Date runDate = holidayService.getBusinessDay(new Date(), true);
		return executor(acountId, runDate);
	}

	public StrategyReport executor(Integer acountId, Date runDate) {
		ExecutMode executMode = (DateUtil.isToday(runDate) && holidayService.isTradeTime()) ? ExecutMode.TRADE_TIME
				: ExecutMode.SPECIFY_TIME;
		ModelContext modelContext = quantAcountService.getModelContext(acountId);
		ExecutorContext context = createExecutorContext(modelContext, executMode, runDate);
		try {
			setSecurityList(context);
			filterSecurityList(context);
			setSmdcData(context);
			setAcountStatus(context);
			setLastStragyReport(context);
			context.setRunLog(new StringBuilder());
	
			Strategy strategy = getStrategyImpl(modelContext);
			strategy.setContext(context);
			StrategyReport report = strategy.handle();
			report.setRunlog(context.getRunLog().toString());
			executorService.saveReport(context, report);
			return report;
		} catch (Exception e) {
			logger.error(DateUtil.toString(runDate, "yyyy/MM/dd")+" "+ e.getMessage(), e);
			return executorService.saveException(context, e);
		}
	}
	

	public void backTest(Integer acountId) {
		backTest(acountId, null);		
	}
	
	public void backTest(Integer acountId, StrategyRunningStatus runnigStatus) {
		ModelContext modelContext = quantAcountService.getModelContext(acountId);
		Date startDate = modelContext.getQuantAcount().getStartDate();
		Date endDate = modelContext.getQuantAcount().getEndDate();
		if(endDate==null || endDate.after(new Date())){
			endDate = new Date();
		}
		startDate = holidayService.getBusinessDay(startDate, false);
		endDate = holidayService.getBusinessDay(endDate, true);
		if(runnigStatus!=null){
			runnigStatus.setDuration(startDate, endDate);
			runnigStatus.beginRun();
		}
		if (quantAcountService.canBackTest(acountId)) {
			executorService.deleteBeforeBackTest(acountId, startDate);
		}
		Strategy strategy = getStrategyImpl(modelContext);

		StrategyReport report = null;
		Date indexDate = startDate;
		ExecutorContext context = createExecutorContext(modelContext, ExecutMode.BACK_TEST, indexDate);
		setSecurityList(context);
		
		while (!indexDate.after(endDate)) {
			try {
				if(runnigStatus!=null){
					runnigStatus.setCurrentDate(indexDate);
				}
				context.setCurrentDate(indexDate);
				filterSecurityList(context); 
				setSmdcData(context);
				setAcountStatus(context);
				context.setLastReport(report);
				context.setRunLog(new StringBuilder());
				executorService.doDiv(context);
				
				strategy.setContext(context);
				report = strategy.handle();
				report.setRunlog(context.getRunLog().toString());
				executorService.saveReport(context, report);
			} catch (Exception e) {
				logger.error(DateUtil.toString(indexDate, "yyyy/MM/dd")+" "+ e.getMessage(), e);
				executorService.saveException(context, e);
			}
			indexDate = holidayService.getNextBusinessDay(indexDate, 1);
		}
		quantAcountService.updateLastBackTest(modelContext.getQuantAcount());
		if(runnigStatus!=null){
			runnigStatus.finishRun();
		}
	}

	@SuppressWarnings("unchecked")
	private Strategy getStrategyImpl(ModelContext modelContext) {
		try {
			Class<Strategy> stClass = (Class<Strategy>) Class.forName(modelContext.getModel().getStrategyClass());
			Strategy strategy = stClass.newInstance();
			return strategy;
		} catch (Exception e) {
			throw new RuntimeException("创建策略失败", e);
		}
	}
	
	private ExecutorContext createExecutorContext(ModelContext modelContext, ExecutMode executMode, Date runDate) {
		ExecutorContext context = new ExecutorContext();
		context.setModelContext(modelContext);
		context.setCurrentDate(DateUtil.floorTime(runDate));
		context.setExecutMode(executMode);
		
		context.setSecurityService(securityService);
		context.setHandicapService(handicapService);
		context.setDivService(divService);
		context.setErService(erService);
		context.setOrderService(executorService);
		context.setReportService(reportService);
		context.setHolidayService(holidayService);
		context.setMarketDailyService(marketDailyService);
		context.setTradeBillService(tradeBillService);
		context.setFundNavPreService(fundNavPreService);
		
		context.setIndicatorService(indicatorService);
		context.setVolatilityService(volatilityService);
		return context;
	}
	
	private void setSecurityList(ExecutorContext context) {
		List<SmdcSecurity> securityList = quantAcountService.getModelSecList(context.getModelContext().getAcountId());
		if (!CollUtil.isEmpty(securityList)) {
			context.setSecurityListNoFilter(securityList);
		} else {
			throw new RuntimeException("security pool config error, no security");
		}
	}
	
	// 上市后20天，退市前10天
	public void filterSecurityList(ExecutorContext context) {
		List<SmdcSecurity> securityList = context.getSecurityListNoFilter();
		List<SmdcSecurity> securityFilter = new ArrayList<>();
		Date testDate = context.getCurrentDate();
		for (SmdcSecurity security : securityList) {
			double listDays = DateUtil.getDays(security.getListDate(), testDate);
			double dlistDays = 100000;
			if(security.getDlistDate()!=null){
				dlistDays = DateUtil.getDays(testDate, security.getDlistDate());
			}
			if(listDays>0&& dlistDays>0){
				securityFilter.add(security);
			}
		}
		context.setSecurityList(securityFilter);
	}
	
	private TradeAcountStatus setAcountStatus(ExecutorContext context) {
		Date date = context.getCurrentDate();
		Integer acountId = context.getModelContext().getAcountId();
		Date lastDate = holidayService.getNextBusinessDay(date, -1);
		TradeAcountStatus lastStatus = acountStatusService.updateAndGet(acountId, lastDate);
		TradeAcountStatus acountStatus = acountStatusService.calculateStatus(date, lastStatus);
		context.setAcountStatus(acountStatus);
		return acountStatus;
	}
	
	private StrategyReport setLastStragyReport(ExecutorContext context) {
		Date lastDay = holidayService.getNextBusinessDay(context.getCurrentDate(), -1);
		StrategyReport lastReport = reportService.getReport(context.getModelContext().getAcountId(), lastDay);
		context.setLastReport(lastReport);
		return lastReport;
	}
	
	private void setSmdcData(ExecutorContext context) {
		List<String> secIds = new ArrayList<String>();
		for (SmdcSecurity security : context.getSecurityList()) {
			secIds.add(security.getSecId());
		}
		Map<String, SmdcMarketDaily> marketMap = marketDailyService.getMarketMap(secIds, context.getCurrentDate());
		context.setMarketDailyMap(marketMap);
		
		Map<String, SmdcEr> erMap = new HashMap<String, SmdcEr>();
		for (SmdcSecurity security : context.getSecurityList()) {
			if (SecurityType.isStock(security.getSecType())) {
				SmdcEr er = erService
						.getPbDateBeforeOne(security, context.getCurrentDate());
				if (er == null) {
					String exceptionInfo = String.format(
							"No er report for [%s] before [%tF]",
							security.getSecId(), context.getCurrentDate());
					throw new RuntimeException(exceptionInfo);
				}
				erMap.put(security.getSecId(), er);
			}
		}
		context.setErMap(erMap);
	}

}
